#include "i2c.h"

#define MODE_INPUT                        0
#define MODE_OUTPUT_SPEED_10MHZ           1
#define MODE_OUTPUT_SPEED_2MHZ            2
#define MODE_OUTPUT_SPEED_50MHZ           3

#define CONF_OUTPUT_GENERAL_PUSH_PULL     0
#define CONF_OUTPUT_GENERAL_OPEN_DRAIN    1
#define CONF_OUTPUT_ALTERNATE_PUSH_PULL   2
#define CONF_OUTPUT_ALTERNATE_OPEN_DRAIN  3

#define CONF_INPUT_ANALOG                 0
#define CONF_INPUT_FLOATING               1
#define CONF_INPUT_PULL_UP_DOWN           2
#define CONF_INPUT_RESERVED               3

// 初始化
void I2C_Init(void)
{
    // 1. 配置时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;

    // 2. GPIO工作模式配置：通用开漏输出 CNF-01，MODE-11
    // PB10配置为开漏输出
    GPIOB->CRH &= ~(0x0F << (2 * 4));
    GPIOB->CRH |= MODE_OUTPUT_SPEED_50MHZ        << (2 * 4);
    GPIOB->CRH |= CONF_OUTPUT_GENERAL_OPEN_DRAIN << (2 * 4 + 2);

    // PB11配置为开漏输出
    GPIOB->CRH &= ~(0x0F << (3 * 4));
    GPIOB->CRH |= MODE_OUTPUT_SPEED_50MHZ        << (3 * 4);
    GPIOB->CRH |= CONF_OUTPUT_GENERAL_OPEN_DRAIN << (3 * 4 + 2);
}

// 发出起始信号
void I2C_Start(void)
{
    // 1. SCL拉高，SDA拉高
    SCL_HIGH;
    SDA_HIGH;
    I2C_DELAY;

    // 2. SCL保持不变，SDA拉低
    SDA_LOW;    // SCL为高电平时，SDA由高变低，表示开始
    I2C_DELAY;

    SCL_LOW;
    I2C_DELAY;
}

// 发出停止信号
void I2C_Stop(void)
{
    SDA_LOW;
    I2C_DELAY;

    SCL_HIGH;
    I2C_DELAY;

    SDA_HIGH;  // SCL为高电平时，SDA由低变高，表示结束
    I2C_DELAY;
}

// 主机发出应答信号
void I2C_Ack(void)
{
    // 1. SDA拉低，输出应答
    SDA_LOW;
    I2C_DELAY;

    // 2. SDA保持不变，SCL拉高，开始数据线上信号采样
    SCL_HIGH;
    I2C_DELAY;

    // 3. SDA保持不变，SCL拉低，结束数据线上信号采样
    SCL_LOW;
    I2C_DELAY;

    // 5. SDA拉高，释放数据总线
    SDA_HIGH;
    I2C_DELAY;
}

// 主机发出非应答信号
void I2C_Nack(void)
{
    // 1. SDA拉高，输出应答
    SDA_HIGH;
    I2C_DELAY;

    // 2. SDA保持不变，SCL拉高，开始数据线上信号采样
    SCL_HIGH;
    I2C_DELAY;

    // 3. SDA保持不变，SCL拉低，结束数据线上信号采样
    SCL_LOW;
    I2C_DELAY;

    // 5. SDA拉高，释放数据总线。
    SDA_HIGH;
    I2C_DELAY;               // 这里不调用也没有关系，因为本来SDA就是高嘛
}

// 主机等待从设备发来应答信号
uint8_t I2C_Wait4Ack(void)
{
    // 1. SCL拉高，开始数据采样
    SCL_HIGH;
    I2C_DELAY;

    // 2. 读取SDA数据线上的电平
    uint16_t ack = READ_SDA;

    // 3. SCL拉低，结束数据采样
    SCL_LOW;
    I2C_DELAY;

    SDA_HIGH;
    I2C_DELAY;  // 这一步不调用也没有关系，原因:
                // 后续如果主机要写数据，那么i2c设备回复了这个ACK后，会释放总线，sda就是高电平
                // 后续如果主机要读数据，那么i2c设备回复了这个ACK后，并不会释放总线，主机拉高sda根本不起作用，SDA是由i2c设备控制的

    return ack ? NACK : ACK;
}

// 主机发送一个字节的数据（写入）
void I2C_SendByte(uint8_t byte)    //高位在前         调用完毕，SCL为低电平，SDA为高电平
{
    for (uint8_t i = 0; i < 8; i++)
    {
        // 1. 取字节的最高位，向SDA写入数据
        if (byte & 0x80)
        {
            SDA_HIGH;
        }
        else
        {
            SDA_LOW;
        }
        I2C_DELAY;

        // 2. SCL拉高，数据采样
        SCL_HIGH;
        I2C_DELAY;

        // 3. SCL拉低，采样结束
        SCL_LOW;
        I2C_DELAY;

        // 4. 左移1位
        byte <<= 1;
    }

    SDA_HIGH;
    I2C_DELAY;    // 最后释放SDA总线，因为发送完一个字节后，SDA总线控制权要交给I2C从设备了
}

// 主机从EEPROM接收一个字节的数据（读取）
uint8_t I2C_ReadByte(void)
{
    // 定义一个变量，用来保存接收的数据
    uint8_t data = 0;

    // 循环处理每一位
    for (uint8_t i = 0; i < 8; i++)
    {
        // 1. SCL拉高，开始采样
        SCL_HIGH;
        I2C_DELAY;

        // 2. 数据采样，读取SDA
        data <<= 1;     // 先做左移，新存入的位永远在最低位
        if (READ_SDA)
        {
            data |= 0x01;   // 先存入最低位，然后每次都左移1位
        }

        // 3. SCL拉低，结束采样
        SCL_LOW;
        I2C_DELAY;
    }

    SDA_HIGH;
    I2C_DELAY;       // 这一步不调用也没有关系，因为接收完最后一个bit后，i2c slave device释放了sda总线，此时sda总线就是高电平。
    return data;
}

/*
共7个基本单元：
void I2C_Start()            调用完毕，SCL为低电平，SDA为低电平

void I2C_SendByte()         调用完毕，SCL为低电平，SDA为高电平
uint8_t I2C_Wait4Ack()      调用完毕，SCL为低电平，SDA为高电平
uint8_t I2C_ReadByte()      调用完毕，SCL为低电平，SDA为高电平
void I2C_Ack()              调用完毕，SCL为低电平，SDA为高电平
void I2C_Nack()             调用完毕，SCL为低电平，SDA为高电平

void I2C_Stop()             调用完毕，SCL为高电平，SDA为高电平
*/


